﻿/* Copyright (C) 2008 Robin Debreuil -- Released under the GNU General Public License (GPL) v2 */

using System;
using System.Text;
//using System.Collections.Generic;
using System.IO;
using ICSharpCode.SharpZipLib.Zip.Compression;

namespace DDW.Swf
{
    public class SwfReader
    {
        private uint pos;
        private int len;
        private byte bitMask;
        private byte[] bytes;

        private static UTF8Encoding utf8Encoder = new UTF8Encoding(false);

        public SwfReader(byte[] rawSwf)
        {
            this.bytes = rawSwf;
            this.len = bytes.Length;
            Reset();
        }
        public uint Position
        {
            get
            {
                return this.pos;
            }
            set
            {
                this.pos = value;
                Align();
            }
        }
        public void Reset()
        {
            pos = 0;
            bitMask = 0x80;
        }
        public bool GetBit()
        {
            bool result = (bytes[pos] & bitMask) != 0;
            bitMask /= 2;
            if (bitMask == 0)
            {
                pos++;
                bitMask = 0x80;
            }
            return result;
        }
        public uint GetBits(uint count)
        {
            uint result = 0;
            for (int i = 0; i < count; i++)
            {
                result = GetBit() ? (result << 1) | 1 : result << 1;
            }
            return result;
        }
        public int GetSignedNBits(uint count)
        {
            uint result = GetBits(count);
            if ((result & (1 << (int)(count - 1))) != 0)
            {
                result |= (uint)(-1 << (int)count);
            }
            return (int)result;
        }
        public byte GetByte()
        {
            Align();
            return bytes[pos++];
        }
        public byte[] GetBytes(uint count)
        {
            Align();
            byte[] byteArray = new byte[count];
            for (int i = 0; i < count; i++)
            {
                byteArray[i] = bytes[pos++];
            }
            return byteArray;
        }
        public byte PeekByte()
        {
            Align();
            return bytes[pos];
        }
        public byte[] PeekBytes(int start, int len)
        {
            Align();
            byte[] result = new byte[len];
            Array.Copy(bytes, start, result, 0, len);
            return result;
        }
        public UInt16 GetUI16()
        {
            Align();
            return (UInt16)((bytes[pos++]) + (bytes[pos++] << 8));
        }
        public Int16 GetInt16()
        {
            Align();
            Int16 result = (Int16)((bytes[pos++]) + (bytes[pos++] << 8));
            return result;
        }
        public Int32 GetInt32()
        {
            Align();
            Int32 result = (Int32)
                (
                    (uint)(bytes[pos++]) +
                    (uint)(bytes[pos++] << 8) +
                    (uint)(bytes[pos++] << 16) +
                    (uint)(bytes[pos++] << 24)
                );
            return result;
        }
        public uint PeekUI16()
        {
            Align();
            return (uint)((bytes[pos]) + (bytes[pos + 1] << 8));
        }
        public uint GetUI32()
        {
            return (uint)(bytes[pos++]) +
                    (uint)(bytes[pos++] << 8) +
                    (uint)(bytes[pos++] << 16) +
                    (uint)(bytes[pos++] << 24);
        }
        public void SkipBytes(uint count)
        {
            Align();
            pos += count;
        }
        public void SkipBits(uint count)
        {
            pos += (uint)(count / 8);
            count = count % 8;
            for (int i = 0; i < count; i++)
            {
                bitMask /= 2;
                if (bitMask == 0)
                {
                    pos++;
                    bitMask = 0x80;
                }
            }
        }
        public float GetFixedNBits(uint nBits)
        {
            float result = this.GetSignedNBits(nBits);
            if (nBits > 1)
            {
                result = result / 0x10000;
            }
            else if (nBits == 1 && result == -1F)
            {
                result = -1F / (float)(0x100000);
            }
            else if (nBits == 1 && result == 0F)
            {
                result = 1F / (float)(0x100000);
            }
            else
            {
                result = result / 0x10000;
            }
            return result;
        }
        public float GetFixed16_16()
        {
            return this.GetFixedNBits(32);
        }
        public float GetFixed8_8()
        {
            float result = this.GetSignedNBits(16);
            return result / 0X100;
        }
        public float GetFloat32()
        {
            byte[] bytes = this.GetBytes(4);
            return System.BitConverter.ToSingle(bytes, 0);
        }
        public double GetDouble()
        {
            byte[] bytes = this.GetBytes(8);
            return System.BitConverter.ToDouble(bytes, 0);
        }

        public void Align()
        {
            if (bitMask != 0x80)
            {
                bitMask = 0x80;
                pos++;
            }
        }
        public string GetString()
        {
            Align();
            uint start = pos;
            char c = (char)bytes[pos++];
            while ((byte)c != 0)
            {
                //result += c;
                c = (char)bytes[pos++];
            }
            uint end = pos;
            pos = start;
            string result = utf8Encoder.GetString(bytes, (int)start, (int)(end - start - 1));
            pos = end;

            return result;
        }
        public string GetString(uint len)
        {
            Align();
            uint start = pos;
            char c = (char)bytes[pos++];
            while ((byte)c != 0)
            {
                c = (char)bytes[pos++];
            }
            uint end = pos;
            pos = start;
            string result = utf8Encoder.GetString(bytes, (int)start, (int)len - 1);
            pos = end;

            return result;
        }
        public void DecompressSwf()
        {
            byte[] buf = new byte[bytes.Length - 8];
            int len = bytes[4] + bytes[5] * 0x100 + bytes[6] * 0x10000 + bytes[7] * 0x1000000;
            byte[] unzipped = new byte[len];
            Array.Copy(bytes, 0, unzipped, 0, 8);
            Array.Copy(bytes, 8, buf, 0, bytes.Length - 8);
            Inflater inf = new Inflater();
            inf.SetInput(buf);
            inf.Inflate(unzipped, 8, len - 8);
            int error = inf.Inflate(unzipped, 8, len - 8);
            unzipped[0] = (byte)'F';
            bytes = unzipped;
        }
    }
    /*
        Signature	UI8		Signature byte:
                            “F” indicates uncompressed
                            “C” indicates compressed (SWF 6 and later only)
        Signature	UI8		Signature byte always “W”
        Signature	UI8		Signature byte always “S”
        Version		UI8		Single byte file version (for example, 0x06 for SWF 6)
        FileLength	UI32	Length of entire file in bytes
        FrameSize	RECT	Frame size in twips
        FrameRate	UI16	Frame delay in 8.8 fixed number of frames per second
        FrameCount	UI16	Total number of frames in file
     */


    public struct SwfHeader
    {
        public bool IsSwf;
        public bool IsCompressed;

        public byte Signature0;
        public byte Signature1;
        public byte Signature2;

        public byte Version;
        public UInt32 FileLength;
        public Rect FrameSize;
        public float FrameRate;
        public UInt16 FrameCount;

        public SwfHeader(SwfReader r)
        {
            this.Signature0 = r.GetByte();

            if (this.Signature0 == 'C')
            {
                this.IsCompressed = true;
                r.DecompressSwf();
            }
            else
            {
                this.IsCompressed = false;
            }

            this.Signature1 = r.GetByte();
            this.Signature2 = r.GetByte();

            this.IsSwf = (Signature2 == 'S') && (Signature1 == 'W') && ((Signature0 == 'C') || (Signature0 == 'F'));

            if (IsSwf)
            {
                this.Version = r.GetByte();
                this.FileLength = r.GetUI32();
                this.FrameSize = new Rect(r);
                UInt16 frate = r.GetUI16();
                this.FrameRate = (frate >> 8) + ((frate & 0xFF) / 0xFF);
                this.FrameCount = r.GetUI16();
            }
            else
            {
                this.Version = 0;
                this.FileLength = 0;
                this.FrameSize = new Rect(0, 0, 0, 0);
                this.FrameRate = 0;
                this.FrameCount = 0;
            }
        }

        public bool Validate()
        {
            return false;
        }
    }
    public struct Rect
    {
        /*
            RECT
            Field	Type		Comment
            Nbits	UB[5]		Bits in each rect value field
            Xmin	SB[Nbits]	x minimum position for rect
            Xmax	SB[Nbits]	x maximum position for rect
            Ymin	SB[Nbits]	y minimum position for rect
            Ymax	SB[Nbits]	y maximum position for rect
        */
        public static readonly Rect Empty = new Rect(0, 0, 0, 0);

        public int XMin;
        public int XMax;
        public int YMin;
        public int YMax;

        public Rect(SwfReader r)
        {
            byte minBits = (byte)r.GetBits(5);
            XMin = r.GetSignedNBits(minBits);
            XMax = r.GetSignedNBits(minBits);
            YMin = r.GetSignedNBits(minBits);
            YMax = r.GetSignedNBits(minBits);
            r.Align();
        }

        public Rect(int xMin, int xMax, int yMin, int yMax)
        {
            this.XMin = xMin;
            this.XMax = xMax;
            this.YMin = yMin;
            this.YMax = yMax;
        }
    }
}
